home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
AmigActive 10
/
AACD 10.iso
/
AACD
/
Online
/
SpeakFreely
/
src
/
reflect.c
< prev
next >
Wrap
C/C++ Source or Header
|
2000-05-18
|
43KB
|
1,444 lines
/*
Speak Freely for Unix
Conference Reflector
Designed and implemented in April of 1998 by John Walker.
Note: this reflector currently works by simple packet replication;
it does not examine the contents of packets or attempt to merge
arriving audio streams in any intelligent fashion. Because it
appears to a connected user as a single connection in Speak
Freely protocol, users must connect in Speak Freely protocol
in order for their audio to be successfully decoded by others.
I've left in complete logic for VAT and RTP connections,
however, in order to make it easier to support those protocols
if, in the future, the reflector is extended to add additional
reflector and/or mixer functionality.
*/
#include "speakfree.h"
#include "version.h"
#include "vat.h"
#define SHOW_SOCKET
#define Reflect_Base 2000 /* Reflector offset from default speaker port */
static int debugforce = 0; /* Debugging forced / prevented ? */
static int whichport = Internet_Port + Reflect_Base; /* Port to listen on (base address) */
static int sock; /* Input socket */
static int ssock; /* Control socket (RTP/VAT) */
static struct sockaddr_in from; /* Sending host address */
static struct sockaddr_in name; /* Address of destination host */
static int fromlen; /* Length of sending host address */
static int showhosts = FALSE; /* Show host names that connect */
static int hosttimeout = 180; /* Consider host idle after this number of seconds */
static char *prog; /* Program name */
static struct sockaddr_in lookhost; /* Look who's listening host, if any */
static char *sdes = NULL; /* RTP SDES packet */
static int sdesl; /* RTP SDES packet length */
static unsigned long ssrc; /* RTP synchronisation source identifier */
static long lwltimer; /* Time next LWL retransmit scheduled */
static int actives = 0; /* Currently active hosts */
#define LWL_RETRANSMIT (5 * 60) /* Seconds between LWL updates */
static struct in_addr lwl_sites[LWL_MAX_SITES]; /* LWL site addresses */
static int lwl_ports[LWL_MAX_SITES]; /* Ports for LWL hosts */
static int lwl_nsites = 0; /* Number of LWL sites published on */
static int monitor = 0; /* Audio monitor ? */
static struct in_addr monitor_site; /* Audio monitor site */
static int monitor_port; /* Audio monitor port */
static struct in_addr local_site; /* IP address running reflector */
static int local_site_known = 0; /* Nonzero if local IP address known */
#ifndef INADDR_LOOPBACK /* Should be defined in netinet/in.h, but just in case */
#define INADDR_LOOPBACK (unsigned long) 0x7F000001L
#endif
static char *htmlFile = NULL; /* HTML file name base */
static int htmlTime = 1 * 60; /* HTML file update time */
static time_t htmlLast = 0; /* HTML last update time */
static int htmlChange = TRUE; /* Change since last HTML update ? */
static int htmlRefresh = 0; /* HTML client-pull refresh interval */
#define HTML_REFRESH "<meta http-equiv=\"Refresh\" content=\"%d\">\n"
#define connection r_connection
/* Open connection state. */
struct connection {
struct connection *con_next; /* Next connection */
struct in_addr con_addr; /* Host Internet address */
long con_timeout; /* Connection timeout */
char con_hostname[264]; /* Host name */
short con_protocol; /* Transmission protocol */
char con_session[4]; /* VAT/RTP session identifier */
char con_uname[4096]; /* User name */
char con_email[256]; /* User E-mail address, if known */
/* Special fields for reflector. */
char *con_compmodes; /* Last compression modes */
int con_control; /* Control packet seen ? */
int con_bye; /* BYE packet received ? */
char con_cname[256]; /* RTCP unique name identifier */
char con_phone[256]; /* User phone number */
char con_loc[1024]; /* User location */
time_t con_ltime; /* Last update time */
};
static struct connection *conn = NULL; /* Chain of current connections */
#define Debug (debugforce != 0) /* Generate debug output */
#define TickTock 7 /* Alarm interval in seconds */
#define TockTock 60 /* Alarm interval when no connections open */
static int crit = FALSE; /* Queue critical section lock */
static int clash = FALSE; /* Critical section clash retry flag */
/* ETIME -- Edit time and date for log messages. */
static char *etime(gmt)
int gmt;
{
struct tm *t;
time_t clock;
static char s[20];
time(&clock);
if (gmt) {
t = gmtime(&clock);
} else {
t = localtime(&clock);
}
sprintf(s, "%d-%02d-%02d %02d:%02d", t->tm_year + 1900, t->tm_mon + 1, t->tm_mday,
t->tm_hour, t->tm_min);
return s;
}
/* COMPRESSIONTYPE -- Return a string describing the type of
compression employed in this buffer. We
attempt to distinguish RTP and VAT
messages from our own here. This isn't
100% reliable in the case of VAT, but
nothing really terrible will happen if
we guess wrong. We only test a packet for
VAT if given the clue that we've seen a
packet arrive on the control channel. */
static char *compressionType(msg, clue)
soundbuf *msg;
int clue;
{
unsigned char *p = (unsigned char *) msg;
if (((p[0] >> 6) & 3) == RTP_VERSION) {
char *r;
switch (p[1] & 0x7F) {
case 0:
r = "RTP PCMU";
break;
case 1:
r = "RTP 1016";
break;
case 2:
r = "RTP G721";
break;
case 3:
r = "RTP GSM";
break;
case 5:
case 6:
r = "RTP DVI4";
break;
case 7:
r = "RTP LPC";
break;
case 8:
r = "RTP PCMA";
break;
case 9:
r = "RTP G722";
break;
case 10:
case 11:
r = "RTP L16";
break;
default:
r = "RTP Unknown";
break;
}
return r;
}
/* Hokey attempt to detect VAT packets. We can't tell
VAT PCMU packets from unmarked Speak Freely packets, so
we let them fall through. */
if (clue && (p[0] & 0xC0) == 0) {
char *r = NULL;
switch (p[1] & 0x7F) {
case VAT_AUDF_GSM:
r = "VAT GSM";
break;
case VAT_AUDF_LPC4:
r = "VAT LPC";
break;
case VAT_AUDF_IDVI:
r = "VAT IDVI";
break;
case VAT_AUDF_L16_16:
case VAT_AUDF_L16_44:
r = "VAT L16";
break;
}
if (r != NULL) {
return r;
}
}
return ((msg->compression & (fComp2X | fCompGSM)) ==
(fComp2X | fCompGSM)) ?
"GSM+2X compressed" :
((msg->compression & (fComp2X | fCompADPCM)) ==
(fComp2X | fCompADPCM)) ?
"ADPCM+2X compressed" :
((msg->compression & (fComp2X | fCompLPC)) ==
(fComp2X | fCompLPC)) ?
"LPC+2X compressed" :
((msg->compression & (fComp2X | fCompLPC10)) ==
(fComp2X | fCompLPC10)) ?
"LPC10+2X compressed" :
((msg->compression & (fComp2X | fCompVOX)) ==
(fComp2X | fCompVOX)) ?
"VOX+2X compressed" :
((msg->compression & fCompADPCM) ? "ADPCM compressed" :
((msg->compression & fCompLPC) ? "LPC compressed" :
((msg->compression & fCompLPC10) ? "LPC10 compressed" :
((msg->compression & fComp2X) ? "2X compressed" :
((msg->compression & fCompGSM) ? "GSM compressed" :
((msg->compression & fCompVOX) ? "VOX compressed" :
"uncompressed"))))));
}
/* MAKESESSIONKEY -- Generate session key. */
static void makeSessionKey(key)
char *key;
{
struct MD5Context md5c;
char s[1024];
s[0] = 0;
/* If you get compile or link errors on references to any of the
following functions, simply comment out the offending lines and
try again. This sequence of code uses a variety of information
about the instantaneous execution environment of the program to
construct a unique session key (or SSRC) to uniquely identify
this sender in RTP packets. The intent is to stir as many
items into the cauldron as possible so that the resulting brew,
cranked through MD5, will have as close a probability as
possible to 1 in 2^32 of duplicating that of another user.
Note that RTP does not guarantee SSRCs are unique. If a
duplication should occur, the result may be confusing but not
disastrous. */
sprintf(s + strlen(s), "%u", getpid());
sprintf(s + strlen(s), "%u", getppid());
V getcwd(s + strlen(s), 256);
sprintf(s + strlen(s), "%lu", clock());
V cuserid(s + strlen(s));
sprintf(s + strlen(s), "%lu", time(NULL));
#ifdef Solaris
sysinfo(SI_HW_SERIAL, s + strlen(s), 12);
#else
sprintf(s + strlen(s), "%u", gethostid());
#endif
getdomainname(s + strlen(s), 256);
gethostname(s + strlen(s), 256);
sprintf(s + strlen(s), "%u", getuid());
sprintf(s + strlen(s), "%u", getgid());
MD5Init(&md5c);
MD5Update(&md5c, s, strlen(s));
MD5Final(key, &md5c);
}
/* SENDLWLMESSAGE -- If enabled, send a message identifying us
to each selected Look Who's Listening server. */
static void sendLwlMessage(dobye)
int dobye;
{
int i, sock;
for (i = 0; i < lwl_nsites; i++) {
if (lwl_ports[i] >= 0) {
sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock < 0) {
perror("opening look who's listening socket");
sdes = NULL;
return;
}
lookhost.sin_port = htons(lwl_ports[i]);
bcopy((char *) (&lwl_sites[i]), (char *) &lookhost.sin_addr.s_addr,
sizeof lookhost.sin_addr.s_addr);
if (connect(sock, (struct sockaddr *) &(lookhost), sizeof lookhost) >= 0) {
if (dobye) {
char v[1024];
int l;
l = rtp_make_bye(v, ssrc, "Exiting sfreflect", FALSE);
if (send(sock, v, l, 0) < 0) {
perror("sending look who's listening BYE packet");
}
if (Debug) {
fprintf(stderr, "%s: sent LWL BYE message to %s/%d.\n", prog,
inet_ntoa(lwl_sites[i]), lwl_ports[i]);
}
} else {
if (send(sock, (char *) sdes, sdesl, 0) < 0) {
perror("sending look who's listening source ID message");
}
if (Debug) {
fprintf(stderr, "%s: sent LWL message to %s/%d.\n", prog,
inet_ntoa(lwl_sites[i]), lwl_ports[i]);
}
}
} else {
perror("connecting look who's listening socket");
}
}
}
lwltimer = LWL_RETRANSMIT;
close(sock);
}
/* MAKEHTML -- Create an HTML file showing current connections. If
"private" is set, exact-match names are included
in the HTML file. */
static void makeHTML(fname, private)
char *fname;
int private;
{
FILE *of;
char f[132], fn[132];
strcpy(f, fname);
strcat(f, ".new");
of = fopen(f, "w");
if (of != NULL) {
struct connection *lw;
#define P(x) fprintf(of, x)
lw = conn;
P("<html>\n<head>\n");
if (htmlRefresh > 0) {
fprintf(of, HTML_REFRESH, htmlRefresh);
}
P("<title>\nSpeak Freely: Sites Active on Reflector\n");
P("</title>\n</head>\n\n<body>\n<center>\n<h1>");
P("Speak Freely: Sites Active on Reflector</h1>\n<h2>");
fprintf(of, "Last change: %s UTC</h2>\n</center>\n<p>\n", etime(TRUE));
if (lw == NULL) {
P("<h2>No sites active.</h2>\n");
} else {
int i = 0;
while (lw != NULL) {
if (!lw->con_bye && (private || ((lw->con_email[0] != '*') &&
(lw->con_cname[0] != '*')))) {
i++;
}
lw = lw->con_next;
}
lw = conn;
if (i == 0) {
/* Fib about number of sites active if all active sites
don't want a directory listing or are in termination. */
P("<h2>No sites active.</h2>\n");
} else {
fprintf(of, "<h2>%d site%s active.</h2>\n",
i, i == 1 ? "" : "s");
P("<pre>\n");
while (lw != NULL) {
if (!lw->con_bye && (private || ((lw->con_email[0] != '*') &&
(lw->con_cname[0] != '*')))) {
char ipport[40];
struct tm *lt;
char s[384];
lt = gmtime(&lw->con_ltime);
sprintf(ipport, "%s:%d", inet_ntoa(lw->con_addr), whichport);
sprintf(s, "\n%-24s %-48s %02d:%02d\n", ipport, lw->con_cname,
lt->tm_hour, lt->tm_min);
outHTML(of, s);
if (lw->con_uname[0] != 0) {
sprintf(s, "%25s%s\n", "", lw->con_uname);
outHTML(of, s);
}
if (lw->con_loc[0] != 0) {
sprintf(s, "%25s%s\n", "", lw->con_loc);
outHTML(of, s);
}
if (lw->con_phone[0] != 0) {
sprintf(s, "%25sPhone: %s\n", "", lw->con_phone);
outHTML(of, s);
}
if (lw->con_email[0] != 0) {
sprintf(s, "%25sE-mail: %s\n", "", lw->con_email);
outHTML(of, s);
}
}
lw = lw->con_next;
}
P("</pre>\n");
}
}
P("</body>\n</html>\n");
fclose(of);
strcpy(fn, fname);
strcat(fn, ".html");
rename(f, fn);
if (Debug) {
fprintf(stderr, "%s: updated %s\n", prog, fn);
}
}
}
/* UPDHTML -- Update HTML if necessary. */
static void updHTML()
{
time_t now;
if ((htmlFile != NULL) && htmlChange &&
((htmlTime <= 0) || (((now = time(NULL)) - htmlLast) > htmlTime))) {
htmlLast = now;
htmlChange = FALSE;
if (htmlFile != NULL) {
/* The reflector always includes names of connected users
in its HTML summary, even if they've flagged their
names not to appear in sflwld's public HTML output.
The logic is that connecting to a reflector is
equivalent to connecting to another user, in which
case one's identity is shown. If you want to permit
"stealth conference members", change the TRUE in the
following line to FALSE. */
makeHTML(htmlFile, TRUE);
}
}
}
/* CHANGED -- Indicate a change which may require updating
the HTML file. */
static void changed()
{
htmlChange = TRUE;
updHTML();
}
/* RELEASE -- Alarm signal-catching function to retransmit
packets at the correct time and to time out
idle hosts. */
static void release()
{
struct connection *c, *l, *n;
if (crit) {
clash = TRUE;
alarm(60);
if (Debug) {
fprintf(stderr, "%s: Critical section clash in release()\n", prog);
}
} else {
int timerStep;
long tdate;
char *hdes = NULL;
int hdesl;
time(&tdate);
/* Mark idle any hosts that have sent us a BYE message or
haven't sent any packets in the last hosttimeout seconds. */
c = conn;
l = NULL;
actives = 0;
while (c != NULL) {
n = c->con_next;
if (!c->con_bye && (tdate < (c->con_timeout + hosttimeout))) {
actives++;
l = c;
if (Debug) {
fprintf(stderr, "Host %s %ld seconds from timeout.\n",
c->con_hostname, (c->con_timeout + hosttimeout) - tdate);
}
} else {
if (showhosts) {
fprintf(stderr, "%s: %s %s idle\n", prog, etime(FALSE), c->con_hostname);
}
if (l == NULL) {
conn = n;
} else {
l->con_next = n;
}
free(c);
htmlChange = TRUE; /* Indicate HTML file update needed */
}
c = n;
}
/* Update our Look Who's Listening information if the
timeout has expired. */
if (sdes != NULL && tdate >= lwltimer) {
sendLwlMessage(FALSE);
lwltimer = tdate + LWL_RETRANSMIT;
}
/* If any hosts are active, send them all a heartbeat
identifying the reflector. */
if (actives > 0) {
struct connection *rc;
hdesl = rtp_make_sdes(&hdes, ssrc, -1, TRUE);
/* Set Speak Freely protocol flag in packet */
hdes[0] = (hdes[0] & 0x3F) | (1 << 6);
rc = conn;
while (rc != NULL) {
/* Once again we have to worry about the case where the
reflector is being accessed from the same machine on
which it is running. In that case, we'll have a
connection in the chain for the local host, since sfmike
is sending us heartbeats and sound, but we don't want
to send heartbeats since they'd loop right back and
wind up in our own input queue. So, test the address
against the local site and skip the heartbeat if they're
the same. The actual heartbeats to the copy of sfspeaker
on the local host are sent in the section of code below
which deals with the monitoring host. */
if (!rc->con_bye &&
(ntohl(rc->con_addr.s_addr) != INADDR_LOOPBACK) &&
(!local_site_known ||
(memcmp(&local_site, &(rc->con_addr), sizeof(struct in_addr)) != 0))) {
struct sockaddr_in pto;
pto.sin_family = AF_INET;
pto.sin_port = htons(whichport + 1);
bcopy(&(rc->con_addr), &pto.sin_addr, sizeof(struct in_addr));
if (sendto(sock, hdes, hdesl,
0, (struct sockaddr *) &pto,
sizeof(struct sockaddr_in)) < 0) {
perror("sending heartbeat");
}
if (Debug) {
fprintf(stderr, "%s: sending heartbeat to %s/%d (%s).\n",
prog, inet_ntoa(rc->con_addr),
whichport + 1, rc->con_hostname);
}
}
rc = rc->con_next;
}
}
/* If there is a monitor host, send it a heartbeat also. */
if (monitor) {
struct sockaddr_in pto;
if (hdes == NULL) {
hdesl = rtp_make_sdes(&hdes, ssrc, -1, TRUE);
/* Set Speak Freely protocol flag in packet */
hdes[0] = (hdes[0] & 0x3F) | (1 << 6);
}
pto.sin_family = AF_INET;
pto.sin_port = htons(monitor_port + 1);
bcopy(&(monitor_site), &pto.sin_addr, sizeof(struct in_addr));
if (sendto(sock, hdes, hdesl,
0, (struct sockaddr *) &pto,
sizeof(struct sockaddr_in)) < 0) {
perror("sending heartbeat");
}
if (Debug) {
fprintf(stderr, "%s: sending heartbeat to monitor host %s/%d.\n",
prog, inet_ntoa(monitor_site),
monitor_port + 1);
}
}
if (hdes != NULL) {
free(hdes);
}
/* Reset the timer based on whether anybody is active. */
if (actives > 0) {
timerStep = TickTock;
} else {
timerStep = TockTock;
}
alarm(timerStep);
if (Debug) {
time_t t;
struct tm *ltd;
time(&t);
ltd = localtime(&t);
fprintf(stderr, "Tick: %2d:%02d %d...\n", ltd->tm_hour, ltd->tm_min, timerStep);
}
}
updHTML(); /* Update HTML if something's changed recently */
signal(SIGALRM, release); /* Reset signal to handle timeout */
}
/* EXITING -- Catch as many program termination signals as
possible and clean up before exit. */
static void exiting()
{
if (sdes) {
/* De-list from LWL server. */
sendLwlMessage(TRUE);
}
exit(0);
}
/* PROG_NAME -- Extract program name from argv[0]. */
static char *prog_name(arg)
char *arg;
{
char *cp = strrchr(arg, '/');
return (cp != NULL) ? cp + 1 : arg;
}
/* USAGE -- Print how-to-call information. */
static void usage()
{
V fprintf(stderr, "%s -- Speak Freely conference reflector.\n", prog);
V fprintf(stderr, " %s.\n", Relno);
V fprintf(stderr, "\n");
V fprintf(stderr, "Usage: %s [options]\n", prog);
V fprintf(stderr, "Options:\n");
V fprintf(stderr, " -D Enable debug output\n");
V fprintf(stderr, " -Hpath Write HTML active site list on base path\n");
V fprintf(stderr, " -Insec Interval between HTML updates\n");
V fprintf(stderr, " -Mhost[:port] Monitor audio on host and port\n");
V fprintf(stderr, " -Pport Listen on given port\n");
V fprintf(stderr, " -Rnsec Client-pull HTML refresh every nsec seconds\n");
V fprintf(stderr, " -U Print this message\n");
V fprintf(stderr, " -Vtimeout Show hostnames that connect\n");
V fprintf(stderr, "\n");
V fprintf(stderr, "by John Walker\n");
V fprintf(stderr, " http://www.fourmilab.ch/\n");
}
/* Main program. */
main(argc, argv)
int argc;
char *argv[];
{
int i, length;
struct soundbuf sb;
struct connection *c;
char *cp;
int newconn;
char md5key[16];
prog = prog_name(argv[0]);
/* First pass option processing. We have to first scan
the options to handle any which affect creation of the
socket. */
for (i = 1; i < argc; i++) {
char *op, opt;
op = argv[i];
if (*op == '-') {
opt = *(++op);
if (islower(opt)) {
opt = toupper(opt);
}
switch (opt) {
case 'P': /* -Pport -- Port to listen on */
whichport = atoi(op + 1);
break;
case 'U': /* -U -- Print usage information */
case '?': /* -? -- Print usage information */
usage();
return 0;
}
} else {
usage();
return 2;
}
}
/* Create the sockets from which to read audio and control packets. */
sock = socket(AF_INET, SOCK_DGRAM, 0);
if (sock < 0) {
perror("opening data socket");
return 1;
}
ssock = socket(AF_INET, SOCK_DGRAM, 0);
if (ssock < 0) {
perror("opening control socket");
return 1;
}
/* Create name with wildcards. */
name.sin_family = AF_INET;
name.sin_addr.s_addr = INADDR_ANY;
name.sin_port = htons(whichport);
if (bind(sock, (struct sockaddr *) &name, sizeof name) < 0) {
perror("binding data socket");
return 1;
}
name.sin_port = htons(whichport + 1);
if (bind(ssock, (struct sockaddr *) &name, sizeof name) < 0) {
perror("binding control socket");
return 1;
}
name.sin_port = htons(whichport);
for (i = 1; i < argc; i++) {
char *op, opt;
op = argv[i];
if (*op == '-') {
opt = *(++op);
if (islower(opt)) {
opt = toupper(opt);
}
switch (opt) {
case 'D': /* -D -- Force debug output */
debugforce = 1;
break;
case 'H': /* -Hname -- HTML file name base path */
htmlFile = op + 1;
break;
case 'I': /* -Isec -- Interval between HTML updates */
htmlTime = atoi(op + 1);
break;
case 'M': /* -Mhost:port -- Monitor on given host and port */
{
char *ep, *cp = op + 1;
long iadr;
struct hostent *h;
monitor_port = Internet_Port;
if ((ep = strchr(cp, ':')) != NULL || (ep = strchr(cp, '/')) != NULL) {
*ep = 0;
monitor_port = atoi(ep + 1);
}
if (isdigit(*cp) && (iadr = inet_addr(cp)) != -1) {
bcopy((char *) &iadr, (char *) (&monitor_site),
sizeof iadr);
monitor = 1;
} else {
h = gethostbyname(cp);
if (h != NULL) {
bcopy((char *) (h->h_addr),
(char *) (&monitor_site),
sizeof(unsigned long));
monitor = 1;
} else {
fprintf(stderr, "%s: warning, monitor host %s unknown.\n",
prog, cp);
}
}
if (Debug && monitor) {
fprintf(stderr, "%s: sending monitor audio to %s/%d: %s.\n", prog,
inet_ntoa(monitor_site), monitor_port, cp);
}
}
break;
case 'R': /* -Rnsec -- Interval between HTML client-pull updates */
htmlRefresh = atoi(op + 1);
break;
case 'V': /* -V -- Show hostnames that connect */
showhosts = TRUE;
if (op[1] != 0) {
int t = atoi(op + 1);
if (t > 0) {
if (t < (TickTock + 1)) {
t = TickTock + 1;
}
hosttimeout = (t / TickTock) * TickTock;
}
}
break;
}
}
}
/* Find assigned port value and print it. */
length = sizeof(name);
if (getsockname(sock, (struct sockaddr *) &name, &length) < 0) {
perror("getting socket name");
return 1;
}
#ifdef SHOW_SOCKET
fprintf(stderr, "%s: socket port #%d\n", prog, ntohs(name.sin_port));
#endif
/* Build the RTCP SDES packet identifying the reflector.
This packet is used both to identify the reflector to
those who connect and, if SPEAKFREE_LWL_TELL is defined,
to register the reflector with the designated look who's
listening hosts. Note that we do *not* include a list
of the identities of currently-connected users in our
SDES, as a pure RTP reflector would do. We rely instead
on an HTML file, which can be accessed by anybody with
a Web browser and does not require special support in
the audio client program. */
makeSessionKey(md5key);
bcopy(md5key, (char *) &ssrc, sizeof ssrc);
sdesl = rtp_make_sdes(&sdes, ssrc, whichport, FALSE);
/* Contact look who's listening host, if requested. */
cp = getenv("SPEAKFREE_LWL_TELL");
if (cp != NULL) {
struct hostent *h;
char *ep, *np;
long iadr;
int n;
lookhost.sin_family = AF_INET;
while (lwl_nsites < LWL_MAX_SITES) {
n = lwl_nsites;
while (*cp != 0 && isspace(*cp)) {
cp++;
}
if (*cp == 0) {
break;
}
if ((np = strchr(cp, ',')) != NULL) {
*np++ = 0;
}
lwl_ports[lwl_nsites] = Internet_Port + 2;
if ((ep = strchr(cp, ':')) != NULL) {
*ep = 0;
lwl_ports[lwl_nsites] = atoi(ep + 1);
}
if (isdigit(*cp) && (iadr = inet_addr(cp)) != -1) {
bcopy((char *) &iadr, (char *) (&lwl_sites[lwl_nsites]),
sizeof iadr);
lwl_nsites++;
} else {
h = gethostbyname(cp);
if (h != NULL) {
bcopy((char *) (h->h_addr),
(char *) (&lwl_sites[lwl_nsites]),
sizeof(unsigned long));
lwl_nsites++;
} else {
fprintf(stderr, "%s: warning, SPEAKFREE_LWL_TELL host %s unknown.\n",
prog, cp);
}
}
if (Debug && lwl_nsites > n) {
fprintf(stderr, "%s: publishing on LWL server %s: %s.\n", prog,
inet_ntoa(lwl_sites[n]), cp);
}
if (np == NULL) {
break;
}
cp = np;
}
if (lwl_nsites > 0) {
sendLwlMessage(FALSE);
}
}
/* Determine the IP address of the host running the
reflector. We need this to avoid duplicating packets
when the reflector is both running and being accessed
from the same machine. */
{
struct hostent *h;
char hname[256];
if (gethostname(hname, sizeof hname) == 0) {
h = gethostbyname(hname);
if (h != NULL) {
bcopy((char *) (h->h_addr), (char *) (&local_site), sizeof(unsigned long));
local_site_known = 1;
if (Debug) {
fprintf(stderr, "%s: running on host %s (%s)\n", prog,
inet_ntoa(local_site), hname);
}
}
}
}
signal(SIGHUP, exiting); /* Set signal to handle termination */
signal(SIGINT, exiting); /* Set signal to handle termination */
signal(SIGTERM, exiting); /* Set signal to handle termination */
signal(SIGALRM, release); /* Set signal to handle timeout */
alarm(TockTock); /* Start periodic timer */
/* Read packets from the sockets. */
while (TRUE) {
int rll;
long pkrtime; /* Time packet received */
fd_set fdset;
int wsock, control;
struct connection *rc = conn;
char *rchat = NULL;
int rchatl;
FD_ZERO(&fdset);
FD_SET(sock, &fdset);
FD_SET(ssock, &fdset);
if (select(FD_SETSIZE, &fdset, NULL, NULL, NULL) <= 0) {
continue;
}
wsock = FD_ISSET(sock, &fdset) ? sock :
(FD_ISSET(ssock, &fdset) ? ssock : -1);
if (wsock < 0) {
continue;
}
control = wsock == ssock;
fromlen = sizeof(from);
if ((rll = recvfrom(wsock, (char *) &sb, sizeof sb, 0, (struct sockaddr *) &from, &fromlen)) < 0) {
if (errno != EINTR) {
perror(!control ? "receiving data packet" :
"receiving control packet");
}
continue;
}
time(&pkrtime);
#ifdef SHOW_SOCKET
if (Debug) {
fprintf(stderr, "%s: %d bytes read from %s socket from %s.\n", prog, rll,
control ? "control" : "data", inet_ntoa(from.sin_addr));
}
#endif
/* See if this connection is active. If not, initialise a new
connection. */
newconn = FALSE;
crit = TRUE; /* Set connection list critical section lock */
c = conn;
while (c != NULL) {
if (memcmp(&from.sin_addr, &(c->con_addr),
sizeof(struct in_addr)) == 0) {
break;
}
c = c->con_next;
}
if (c == NULL) {
c = (struct connection *) malloc(sizeof(struct connection));
if (c != NULL) {
struct hostent *h;
newconn = TRUE;
c->con_next = conn;
conn = c;
bcopy(&from.sin_addr, &(c->con_addr),
sizeof(struct in_addr));
h = gethostbyaddr((char *) &from.sin_addr, sizeof(struct in_addr),
AF_INET);
if (h == NULL) {
strcpy(c->con_hostname, inet_ntoa(from.sin_addr));
} else {
strcpy(c->con_hostname, h->h_name);
}
}
} else if (c->con_timeout == -1) {
newconn = TRUE;
}
/* Initialise fields in connection. Only fields which need to
be reinitialised when a previously idle host resumes activity
need be set here. */
if (newconn) {
c->con_compmodes = NULL;
c->con_control = FALSE;
c->con_bye = FALSE;
c->con_ltime = time(NULL);
/* If this is the first connection after a period of
inactivity, make sure the timer loop is running at
the faster rate we used when one or more connections
are active. */
if (actives == 0) {
alarm(TickTock);
}
}
if (c != NULL) {
/* Reset connection timeout. */
c->con_timeout = pkrtime;
if (newconn || c->con_bye) {
if (showhosts) {
fprintf(stderr, "%s: %s %s connect\n", prog, etime(FALSE), c->con_hostname);
}
c->con_bye = FALSE;
}
} else {
crit = FALSE;
if (clash) {
release();
}
continue;
}
/* If this is a control channel packet, examine it to
determine if this is a new connection, protocol
change, end of connection and update the connection
list accordingly. */
if (control) {
short protocol = PROTOCOL_VATRTP_CRYPT;
unsigned char *p = (unsigned char *) &sb;
unsigned char *apkt;
int proto = (p[0] >> 6) & 3;
if (proto == 0) {
/* To avoid spoofing by bad encryption keys, require
a proper ID message be seen before we'll flip into
VAT protocol. */
if (((p[1] == 1) || (p[1] == 3)) ||
((c->con_protocol == PROTOCOL_VAT) && (p[1] == 2))) {
protocol = PROTOCOL_VAT;
bcopy(p + 2, c->con_session, 2); /* Save conference ID */
if (p[1] == 1) {
char uname[256];
bcopy(p + 4, uname, rll - 4);
uname[rll - 4] = 0;
if (strcmp(uname, c->con_uname) != 0) {
strcpy(c->con_uname, uname);
if (showhosts) {
fprintf(stderr, "%s: %s sending from %s.\n", prog,
c->con_uname, c->con_hostname);
}
changed();
}
}
/* Handling of VAT IDLIST could be a lot more elegant
than this. */
if (p[1] == 3) {
char *uname;
uname = (char *) malloc(rll);
if (uname != NULL) {
unsigned char *bp = p, *ep = p + rll;
int i = bp[4];
bp += 8;
uname[0] = 0;
*ep = 0;
while (--i >= 0 && bp < ep) {
bp += 4;
strcat(uname, "\t");
strcat(uname, (char *) bp);
while (isspace(uname[strlen(uname) - 1])) {
uname[strlen(uname) - 1] = 0;
}
strcat(uname, "\n");
bp += (strlen((char *) bp) + 3) & ~3;
}
if (strncmp(uname, c->con_uname, (sizeof c->con_uname - 1)) != 0) {
strncpy(c->con_uname, uname, sizeof c->con_uname);
if (strlen(uname) > ((sizeof c->con_uname) - 1)) {
c->con_uname[((sizeof c->con_uname) - 1)] = 0;
}
if (showhosts) {
fprintf(stderr, "%s: now in conference at %s:\n%s", prog,
c->con_hostname, uname);
}
changed();
}
free(uname);
}
}
/* If it's a DONE packet, reset protocol to unknown. */
if (p[1] == 2) {
c->con_protocol = protocol = PROTOCOL_UNKNOWN;
c->con_timeout = -1;
c->con_uname[0] = c->con_email[0] = 0;
c->con_bye = TRUE;
if (showhosts) {
fprintf(stderr, "%s: %s VAT connection closed.\n",
prog, c->con_hostname);
}
changed();
}
}
} else if (proto == RTP_VERSION || proto == 1) {
if (isValidRTCPpacket((unsigned char *) &sb, rll)) {
protocol = (proto == 1) ? PROTOCOL_SPEAKFREE : PROTOCOL_RTP;
bcopy(p + 4, c->con_session, 4); /* Save SSRC */
c->con_ltime = time(NULL);
/* If it's a BYE packet, reset protocol to unknown. */
if (isRTCPByepacket((unsigned char *) &sb, rll)) {
c->con_protocol = protocol = PROTOCOL_UNKNOWN;
c->con_timeout = -1;
c->con_uname[0] = c->con_email[0] = 0;
c->con_bye = TRUE;
if (showhosts) {
fprintf(stderr, "%s: %s %s connection closed.\n",
prog, c->con_hostname,
proto == 1 ? "Speak Freely" : "RTP");
}
changed();
/* If it's a text chat message, reformat it to
be forwarded to all connected hosts. */
} else if (isRTCPAPPpacket((unsigned char *) &sb, rll,
RTCP_APP_TEXT_CHAT, &apkt) && apkt != NULL) {
char *ident = c->con_hostname,
*echat = NULL;
/* To identify the sender, get successively more
personal depending on the information we have at
hand, working down from hostname (which may just
be an IP address if we couldn't resolve the host,
through E-mail address, to user name. */
if (c->con_email[0] != 0) {
ident = c->con_email;
}
if (c->con_uname[0] != 0) {
ident = ident = c->con_uname;
}
if (Debug) {
printf("%s: Text chat received \"%s: %s\"\n",
c->con_hostname, ident, (char *) (apkt + 12));
}
echat = malloc(strlen(ident) +
strlen((char *) (apkt + 12)) + 8);
if (echat != NULL) {
rchat = malloc(strlen(echat) + 256);
if (rchat != NULL) {
sprintf(echat, "%s: %s", ident, (char *) (apkt + 12));
rchatl = rtp_make_app(rchat, ssrc, TRUE, RTCP_APP_TEXT_CHAT, echat);
/* Set Speak Freely protocol flag in packet */
rchat[0] = (rchat[0] & 0x3F) | (1 << 6);
}
free(echat);
}
/* Otherwise, it's presumably an SDES, from which we
should update the user identity information for the
connection. */
} else {
struct rtcp_sdes_request rp;
rp.nitems = 5;
rp.item[0].r_item = RTCP_SDES_CNAME;
rp.item[1].r_item = RTCP_SDES_NAME;
rp.item[2].r_item = RTCP_SDES_EMAIL;
rp.item[3].r_item = RTCP_SDES_PHONE;
rp.item[4].r_item = RTCP_SDES_LOC;
if (parseSDES((unsigned char *) &sb, &rp)) {
char cname[256], uname[4096], email[256], phone[256], location[1024];
cname[0] = uname[0] = email[0] = phone[0] = location[0] = 0;
if (rp.item[1].r_text != NULL) {
copySDESitem(rp.item[1].r_text, uname);
if (rp.item[2].r_text != NULL) {
copySDESitem(rp.item[2].r_text, email);
} else if (rp.item[2].r_text != NULL) {
copySDESitem(rp.item[0].r_text, email);
}
} else if (rp.item[2].r_text != NULL) {
copySDESitem(rp.item[2].r_text, uname);
} else if (rp.item[0].r_text != NULL) {
copySDESitem(rp.item[0].r_text, uname);
}
if (rp.item[3].r_text != NULL) {
copySDESitem(rp.item[3].r_text, phone);
}
if (rp.item[4].r_text != NULL) {
copySDESitem(rp.item[4].r_text, location);
}
if (rp.item[0].r_text != NULL) {
copySDESitem(rp.item[0].r_text, cname);
}
if (strcmp(cname, c->con_cname) != 0 ||
strcmp(uname, c->con_uname) != 0 ||
strcmp(email, c->con_email) != 0 ||
strcmp(phone, c->con_phone) != 0 ||
strcmp(location, c->con_loc) != 0
) {
strcpy(c->con_cname, cname);
strcpy(c->con_uname, uname);
strcpy(c->con_email, email);
strcpy(c->con_phone, phone);
strcpy(c->con_loc, location);
if (uname[0] != 0) {
if (showhosts) {
fprintf(stderr, "%s: %s", prog, uname);
if (email[0] != 0) {
fprintf(stderr, " (%s)", email);
}
fprintf(stderr, " sending from %s.\n",
c->con_hostname);
}
changed();
}
}
}
}
} else {
if (Debug) {
fprintf(stderr, "Invalid RTCP packet received.\n");
}
}
} else {
if (Debug) {
fprintf(stderr, "Bogus protocol 3 control message.\n");
}
}
/* If protocol changed, update in connection and, if appropriate,
update the reply command. */
if (protocol != c->con_protocol) {
static char *pname[] = {
"Speak Freely",
"VAT",
"RTP",
"VAT/RTP encrypted",
"Unknown"
};
c->con_protocol = protocol;
if (showhosts) {
fprintf(stderr, "%s: %s sending in %s protocol.\n",
prog, c->con_hostname, pname[protocol]);
}
}
/* Done processing control packet. We never reflect
non-chat control packets, just use them to update our own
list of connections. */
crit = FALSE;
if (clash) {
release();
}
if (!rchat) {
continue;
}
}
crit = FALSE;
/* If this message is tagged with our Speak Freely protocol
bit, force protocol back to Speak Freely. This allows us
to switch back to Speak Freely after receiving packets in
VAT. We can still get confused if we receive a packet from
an older version of Speak Freely that doesn't tag. */
if (c->con_protocol == PROTOCOL_VAT ||
c->con_protocol == PROTOCOL_VATRTP_CRYPT) {
unsigned char *p = (unsigned char *) &sb;
if (((p[0] >> 6) & 3) == 1) {
c->con_protocol = PROTOCOL_SPEAKFREE;
}
}
/* If this is not a sound packet (show your face packet,
PGP key, etc.) discard it. These point-to-point facilities
won't work and will probably disrupt a conference. */
if ((c->con_protocol == PROTOCOL_SPEAKFREE) && (!rchat) &&
(!isSoundPacket(ntohl(sb.compression)))) {
if (Debug) {
fprintf(stderr, "%s: discarding %d byte control packet (flags 0x%08lX) from %s/%d.\n",
prog, rll, (long) ntohl(sb.compression), inet_ntoa(c->con_addr),
whichport);
}
continue;
}
/* If we're forwarding a text chat packet, copy it into the
buffer which we forward. */
if (rchat) {
rll = rchatl;
bcopy(rchat, &sb, rll);
}
/* This is a data channel packet. Reflect it back to all
active hosts, excepting of course the one which sent
it. Packets are not to sent to hosts which have sent
us a BYE packet and not affirmatively re-connected. */
if (Debug) {
fprintf(stderr, "%s: reflecting %d bytes from %s/%d (%s).\n",
prog, rll, inet_ntoa(c->con_addr), whichport, c->con_hostname);
}
while (rc != NULL) {
/* The test involving local_site in the following statement
is to avoid sending a packet to ourselves when the same
machine is used to run the reflector and connect to it,
using the "monitor site" facility. In this circumstance
there will be a connection in the connection chain for
the local copy of sfmike sending to the reflector. But
if we send to this address, what will actually happen is
that the reflector (namely us) will receive the packet and,
unless it originates from the local machine, re-echo it
to all the connected hosts, resulted in stut-stut-stuttering
sound. Since we know that we're in fact listening to
whichport on this host, there is no circumstance in which
we should ever send a packet on that port to the local host. */
if (rc != c && !(rc->con_bye) &&
(ntohl(rc->con_addr.s_addr) != INADDR_LOOPBACK) &&
(!local_site_known ||
(memcmp(&local_site, &(rc->con_addr), sizeof(struct in_addr)) != 0))) {
struct sockaddr_in pto;
pto.sin_family = AF_INET;
pto.sin_port = htons(whichport + (rchat ? 1 : 0));
bcopy(&(rc->con_addr), &pto.sin_addr, sizeof(struct in_addr));
if (sendto(sock, (char *) &sb, rll,
0, (struct sockaddr *) &pto,
sizeof(struct sockaddr_in)) < 0) {
perror("reflecting sound packet");
}
if (Debug) {
fprintf(stderr, "%s: ... reflecting to %s/%d (%s).\n",
prog, inet_ntoa(rc->con_addr), whichport + (rchat ? 1 : 0), rc->con_hostname);
}
}
rc = rc->con_next;
}
/* If audio monitoring is requested, send the packet to the
monitoring host as well. If the originating site is the
same as the monitoring host we don't forward the packet.
This case occurs when the same machine is running the
reflector and connected to it. */
if (monitor &&
(ntohl(c->con_addr.s_addr) != INADDR_LOOPBACK) &&
(!local_site_known ||
(memcmp(&local_site, &(c->con_addr), sizeof(struct in_addr)) != 0))) {
struct sockaddr_in pto;
pto.sin_family = AF_INET;
pto.sin_port = htons(monitor_port + (rchat ? 1 : 0));
bcopy(&monitor_site, &pto.sin_addr, sizeof(struct in_addr));
if (sendto(sock, (char *) &sb, rll,
0, (struct sockaddr *) &pto,
sizeof(struct sockaddr_in)) < 0) {
perror("echoing sound packet to monitor host");
}
if (Debug) {
fprintf(stderr, "%s: ... sending to monitor host to %s/%d.\n",
prog, inet_ntoa(monitor_site), monitor_port + (rchat ? 1 : 0));
}
}
if (rchat) {
free(rchat);
rchat = NULL;
}
/* If verbose mode is selected, indicate any change in
protocol or compression mode by this connection. */
c->con_control |= control;
if (showhosts && !control) {
char *cmodes = compressionType(&sb, c->con_control);
if (c->con_compmodes != cmodes) {
c->con_compmodes = cmodes;
fprintf(stderr, "%s: %s sending %s.\n", prog, c->con_hostname,
cmodes);
}
}
crit = FALSE;
if (clash) {
release();
}
}
#ifdef MEANS_OF_EXIT
close(sock);
exiting();
return 0;
#endif
}